﻿using System;
using HK.Core.Network.Download.Data;
using UnityEngine;

namespace HK.Core.Utils
{
	/// <summary>
	/// 工具类:数学相关.
	/// </summary>
    public static class UtilsMath
    {
	    
#region Convert

#region Bytes

		public const string BYTE_UNIT_BYTES = "Bytes";
		private const float KB_BYTES = 1024.0f;
		public const string KB_UNIT_BYTES = "Kb";
		private const float MB_BYTES = 1024.0f * KB_BYTES;
		public const string MB_UNIT_BYTES = "Mb";
		private const float GB_BYTES = 1024.0f * MB_BYTES;
		public const string GB_UNIT_BYTES = "Gb";

		/// <summary>
		/// 取得数据大小 - 显示用
		/// </summary>
		/// <param name="iTotalSize">数据大小（单位：Bytes）</param>
		/// <param name="iIncludeDelimiter">是否包含分隔符标志位</param>
		/// <returns>数据大小</returns>
		public static string GetDataDisplaySize(long iTotalSize, bool iIncludeDelimiter = true)
		{
			var dataSizeValue = GetDataSize(iTotalSize, out var oUnit);
			if (iIncludeDelimiter)
			{
				return $"{dataSizeValue:N2} {oUnit}";
			}

			return $"{dataSizeValue:0.00} {oUnit}";
		}
		
		/// <summary>
		/// 取得数据大小 - 单精度
		/// </summary>
		/// <param name="iTotalSize">数据大小（单位：Bytes）</param>
		/// <param name="oUnit">单位：Bytes/Kb/Mb/Gb</param>
		/// <returns>数据大小（单位：Gb/Mb/Kb/Bytes）</returns>
		public static float GetDataSize(long iTotalSize, out string oUnit)
		{
			oUnit = BYTE_UNIT_BYTES;
			float ret = iTotalSize;
			if (GB_BYTES < iTotalSize)
			{
				ret = ConvertByteToGb(iTotalSize);
				oUnit = GB_UNIT_BYTES;
			}
			else if (MB_BYTES < iTotalSize)
			{
				ret = ConvertByteToMb(iTotalSize);
				oUnit = MB_UNIT_BYTES;
			}
			else if (KB_BYTES < iTotalSize)
			{
				ret = ConvertByteToKb(iTotalSize);
				oUnit = KB_UNIT_BYTES;
			}

			return ret;
		}
		
		/// <summary>
		/// 转换字节（Bytes -> Kb）
		/// </summary>
		/// <param name="iByteVale">字节数</param>
		/// <returns>Kb</returns>
		public static float ConvertByteToKb(long iByteVale)
		{
			var mbValue = 0.0f;
			if (0L < iByteVale)
			{
				mbValue = iByteVale / KB_BYTES;
			}

			return mbValue;
		}

		/// <summary>
		/// 转换字节（Kb -> Bytes）
		/// </summary>
		/// <param name="iKbVale">Kb数</param>
		/// <returns>Bytes数</returns>
		public static long ConvertKbToBytes(long iKbVale)
		{
			return (long)(iKbVale * KB_BYTES);
		}

		/// <summary>
		/// 转换字节（Bytes -> Mb）
		/// </summary>
		/// <param name="iByteVale">字节数</param>
		/// <returns>Mb</returns>
		public static float ConvertByteToMb(long iByteVale)
		{
			var mbValue = 0.0f;
			if (0L < iByteVale)
			{
				mbValue = iByteVale / MB_BYTES;
			}
			return mbValue;
		}

		/// <summary>
		/// 转换字节（Mb -> Bytes）
		/// </summary>
		/// <param name="iMbVale">Mb数</param>
		/// <returns>Bytes数</returns>
		public static long ConvertMbToBytes(long iMbVale)
		{
			return (long)(iMbVale * MB_BYTES);
		}
		
		/// <summary>
		/// 转换字节（Bytes -> Gb）
		/// </summary>
		/// <param name="iByteVale">字节数</param>
		/// <returns>Gb</returns>
		public static float ConvertByteToGb(long iByteVale)
		{
			var mbValue = 0.0f;
			if (0L < iByteVale)
			{
				mbValue = iByteVale / GB_BYTES;
			}

			return mbValue;
		}

		/// <summary>
		/// 转换字节（Gb -> Bytes）
		/// </summary>
		/// <param name="iGbVale">Gb数</param>
		/// <returns>Bytes数</returns>
		public static long ConvertGbToBytes(long iGbVale)
		{
			return (long)(iGbVale * GB_BYTES);
		}

		/// <summary>
		/// 根据数据大小取得数据单位
		/// </summary>
		/// <param name="iDataSize">数据大小(单位:Byte)</param>
		/// <returns>数据单位</returns>
		public static string GetUnitByDataSize(long iDataSize)
		{
			if (GB_BYTES < iDataSize)
			{
				return GB_UNIT_BYTES;
			}
			
			if (MB_BYTES < iDataSize)
			{
				return MB_UNIT_BYTES;
			}

			return KB_BYTES < iDataSize ? KB_UNIT_BYTES : BYTE_UNIT_BYTES;
		}

#endregion

#endregion

#region Math

		/// <summary>
		/// 判断数字是否为某数的平方数
		///   备注:
		///     n^2 = (n-1)^2 +2(n-1) + 1
		///     n^2 = (n-1)^2 +(2n -1)
		///     n^2 = (n-2)^2 + (2(n-1) -1) + (2n-1)
		///     ······
		///     推导得到公式：
		///       n^2 = 1+3+5+7+······+(2n-1)
		/// </summary>
		/// <param name="iNum"></param>
		/// <returns>true : 是平方数; false : 不是平方数;</returns>
		public static bool IsSquare(int iNum)
		{
			// 变化步长为2，初值为1，一直减到num不再大于0
			for (var i = 1; iNum > 0; i += 2)
			{
				iNum -= i;
			}
			// 如果num减到最后，恰好等于0，就是平方数；反之，就不是
			return iNum == 0;
		}

		/// <summary>
		/// 判断是不是为2的N次幂
		/// </summary>
		/// <param name="iNum">数字</param>
		/// <returns>true : 是; false : 否;</returns>
		public static bool IsPowerOf2(int iNum)
		{
			if (iNum < 1) return false;
			return (iNum & iNum - 1) == 0;
		}

		/// <summary>
		/// 判断字符是否为数字
		/// </summary>
		/// <param name="iStr">字符串</param>
		/// <returns>true : 是; false : 否;</returns>
		public static bool IsNumber(string iStr)
		{
			if (string.IsNullOrEmpty(iStr) || 0 < iStr.Length) return false;
			var chars = iStr.ToCharArray();
			var count = chars.Length;
			var flg = true;
			for (var idx = 0; idx < count; ++idx)
			{
				var charTmp = chars[idx];
				flg = IsNumber(charTmp);
				if(!flg) break;
			}

			return flg;
		}
		
		/// <summary>
		/// 判断字符是否为数字
		/// </summary>
		/// <param name="iC">字符</param>
		/// <returns>true : 是; false : 否;</returns>
		public static bool IsNumber(char iC)
		{
			return iC >= '0' && iC <= '9';
		}
		
		/// <summary>
		/// 计数百分比(0.0f~1.0f)
		/// </summary>
		/// <param name="iNum">数量</param>
		/// <param name="iMax">最大数</param>
		/// <returns>百分比(0.0f~1.0f)</returns>
		public static float CalcPercent(long iNum, long iMax)
		{
			if (0L == iMax) return 0.0f;
			var progress = (float)iNum / iMax;
			return Mathf.Clamp(progress, 0.0f, 1.0f);
		}

		/// <summary>
		/// 计数百分比(0.0f~1.0f)
		/// </summary>
		/// <param name="iNum">数量</param>
		/// <param name="iMax">最大数</param>
		/// <returns>百分比(0.0f~1.0f)</returns>
		public static float CalcPercent(int iNum, int iMax)
		{
			if (0 == iMax) return 0.0f;
			var progress = (float)iNum / iMax;
			return Mathf.Clamp(progress, 0.0f, 1.0f);
		}

		/// <summary>
		/// 取得百分比文本
		/// </summary>
		/// <param name="iProgress">进度信息(0.0f～1.0f)</param>
		/// <param name="iFormat">格式:如:"{0:N}%"</param>
		/// <returns></returns>
		public static string GetPercentText0(float iProgress, string iFormat = null)
		{
			var progress = iProgress * 100;
			var format = iFormat;
			if (string.IsNullOrEmpty(format))
			{
				// 保留小数点后0位
				format = "{0:N0}%";
			}
			return string.Format(format, progress);
		}
		
		/// <summary>
		/// 取得百分比文本<BR/>
		/// * 保留小数点后1位
		/// </summary>
		/// <param name="iProgress">进度信息(0.0f～1.0f)</param>
		/// <returns></returns>
		public static string GetPercentText1(float iProgress)
		{
			return GetPercentText0(iProgress,"{0:N1}%");
		}
		
		/// <summary>
		/// 取得百分比文本<BR/>
		/// * 保留小数点后2位
		/// </summary>
		/// <param name="iProgress">进度信息(0.0f～1.0f)</param>
		/// <returns></returns>
		public static string GetPercentText2(float iProgress)
		{
			return GetPercentText0(iProgress,"{0:N2}%");
		}
		
		/// <summary>
		/// 取得百分比文本
		/// </summary>
		/// <param name="iCurValue">当前值</param>
		/// <param name="iTotalValue">最大值</param>
		/// <param name="iFormat">格式:如:"{0:N}"</param>
		/// <returns></returns>
		public static string GetPercentText(int iCurValue, int iTotalValue, string iFormat = null)
		{
			var progress = CalcPercent(iCurValue, iTotalValue) * 100.0f;
			var format = iFormat;
			if (string.IsNullOrEmpty(format))
			{
				// 保留小数点后0位
				format = "{0:N0}%";
			}
			return string.Format(format, progress);
		}
		
		/// <summary>
		/// 取得百分比文本
		/// </summary>
		/// <param name="iCurValue">当前值</param>
		/// <param name="iTotalValue">最大值</param>
		/// <param name="iFormat">格式:如:"{0:N}"</param>
		/// <returns></returns>
		public static string GetPercentText(long iCurValue, long iTotalValue, string iFormat = null)
		{
			var progress = CalcPercent(iCurValue, iTotalValue) * 100.0f;
			var format = iFormat;
			if (string.IsNullOrEmpty(format))
			{
				// 保留小数点后0位
				format = "{0:N0}%";
			}
			return string.Format(format, progress);
		}
		
#endregion

#region Download

		/// <summary>
		/// 计算下载速度
		/// </summary>
		/// <param name="iDataSize">数据大小(单位:Byte)</param>
		/// <param name="iCostTime">耗时(单位:毫秒)</param>
		/// <param name="oByteSpeed">下载速度(Byte/s)</param>
		/// <returns>下载速度</returns>
		private static string CalcDownloadSpeed(long iDataSize, long iCostTime, out float oByteSpeed)
		{
			oByteSpeed = 0.0f;
			if (0L >= iDataSize || 0 >= iCostTime) return null;
			var costTime = iCostTime / 1000.0f;
			oByteSpeed = iDataSize / costTime;
			
			var speedTxt = GetDataDisplaySize((int)oByteSpeed, false);
			return $"{speedTxt}/s";
		}
		
		/// <summary>
		/// 计算下载速度
		/// </summary>
		/// <param name="iSpeed">速度(单位:Byte/毫秒)</param>
		/// <returns>下载速度</returns>
		private static string CalcDownloadSpeed(float iSpeed, out float oByteSpeed)
		{
			oByteSpeed = 0.0f;
			if (0L >= iSpeed) return null;
			oByteSpeed = iSpeed * 1000.0f;
			
			var speedTxt = GetDataDisplaySize((int)oByteSpeed, false);
			return $"{speedTxt}/s";
		}
		
		/// <summary>
		/// 下载显示选项
		/// </summary>
		[Flags]
		public enum DownloadDisplayOptions
		{
			/// <summary>
			/// 百分比
			/// </summary>
			Percent = 0x00000001,
			
			/// <summary>
			/// 计数信息
			/// </summary>
			Count = 0x00000002,
			
			/// <summary>
			/// 数据大小信息
			/// </summary>
			DataSize = 0x00000004,
			
			/// <summary>
			/// 下载速度
			/// </summary>
			Speed = 0x00000008,
			
			/// <summary>
			/// 预计剩余时间
			/// </summary>
			RestTime = 0x00000010,
			
			/// <summary>
			/// 耗时
			/// </summary>
			CostTime = 0x00000020,
			
			/// <summary>
			/// 默认显示
			/// </summary>
			Default = Percent | Count | DataSize | Speed,
			
			/// <summary>
			/// 完全格式
			/// </summary>
			Full = Default | RestTime | CostTime
		}
		
		/// <summary>
		/// 取得显示用下载详细信息
		/// </summary>
		/// <param name="iDownloadedCount">已下载个数</param>
		/// <param name="iTotalCount">下载总数</param>
		/// <param name="iDownloadedSize">已下载大小(单位:Byte)</param>
		/// <param name="iTotalSize">下载总大小(单位:Byte)</param>
		/// <param name="iSpeedInfo">速度项强</param>
		/// <param name="oProgress">下载进度</param>
		/// <param name="iOptions">显示选项</param>
		/// <param name="iTimeLen">时间显示程度</param>
		/// <returns>显示用下载详细信息</returns>
		public static string GetDownloadDisplayTxt(
			int iDownloadedCount, int iTotalCount, long iDownloadedSize, long iTotalSize, 
			DownloadSpeed iSpeedInfo, out float oProgress, int iOptions = (int)DownloadDisplayOptions.Default,
			int iTimeLen = 1)
		{
			oProgress = CalcPercent(iDownloadedSize, iTotalSize);
			var totalCostTime = iSpeedInfo.TotalCostTime;
			
			// 显示百分比
			var displayTxt = string.Empty;
			if (UtilsBytes.CheckBytes(iOptions, (int)DownloadDisplayOptions.Percent))
			{
				displayTxt = GetPercentText0(oProgress);
				displayTxt = $"[{displayTxt}]";
			}

			// 计数
			if (UtilsBytes.CheckBytes(iOptions, (int)DownloadDisplayOptions.Count))
			{
				displayTxt = $"{displayTxt} {iDownloadedCount}/{iTotalCount}";
			}
			
			// 数据大小
			if (UtilsBytes.CheckBytes(iOptions, (int)DownloadDisplayOptions.DataSize))
			{
				var downloadedSizeTxt = GetDataDisplaySize(iDownloadedSize, false);
				var totalSizeTxt = GetDataDisplaySize(iTotalSize, false);
				displayTxt = $"{displayTxt}({downloadedSizeTxt}/{totalSizeTxt})";
			}
            
			// 下载速度
			var byteSpeed = 0.0f;
			if (UtilsBytes.CheckBytes(iOptions, (int)DownloadDisplayOptions.Speed))
			{
				// 计算下载速度(单位:Byte/s)
				var downloadSpeed = iSpeedInfo.CalcDownloadSpeed();
				var downloadSpeedTxt = CalcDownloadSpeed(downloadSpeed, out byteSpeed);
				displayTxt = $"{displayTxt} {downloadSpeedTxt}";
			}
			
			// 预计剩余时间
			if (UtilsBytes.CheckBytes(iOptions, (int)DownloadDisplayOptions.RestTime))
			{
				var restSize = iTotalSize - iDownloadedSize;
				if (0.0f < byteSpeed && 0 < restSize)
				{
					// 预计剩余事件(单位:秒)
					var restTime = (int)(restSize / byteSpeed);
					// 低于1秒的 都显示0秒
					restTime = 0 >= restTime ? 0 : restTime;
					restTime *= 1000;

					var restTimeTxt = UtilsTime.ConvertToDisplayTime(restTime, false, iTimeLen);
					if (!string.IsNullOrEmpty(restTimeTxt))
					{
						displayTxt = $"{displayTxt} (Rest:{restTimeTxt})";
					}
				}
			}
			
			// 耗时
			if (UtilsBytes.CheckBytes(iOptions, (int)DownloadDisplayOptions.RestTime))
			{
				var costTimeTxt = UtilsTime.ConvertToDisplayTime(totalCostTime, false, iTimeLen);
				if (!string.IsNullOrEmpty(costTimeTxt))
				{
					displayTxt = $"{displayTxt} (Cost:{costTimeTxt})";
				}
			}
			
			return displayTxt;
		}

#endregion

    }
}
